package com.derbysoft.synxisadapter.dswitch2.service.impl;

import com.derbysoft.remote.dswitch.core.GenericRQ;
import com.derbysoft.remote.dswitch.generic.constants.UniqueIDType;
import com.derbysoft.remote.dswitch.hotel.dto.CancelRQ;
import com.derbysoft.remote.dswitch.hotel.dto.HotelAvailRQ;
import com.derbysoft.remote.dswitch.hotel.dto.HotelMultiAvailRQ;
import com.derbysoft.remote.dswitch.hotel.dto.HotelReservationRQ;
import com.derbysoft.synxisadapter.commons.model.*;
import com.derbysoft.synxisadapter.dswitch2.translator.impl.TranslateHelper;
import com.derbysoft.synxisadapter.commons.utils.XMLUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

public class LogInfo {
    private static Logger logger = LoggerFactory.getLogger(LogInfo.class);
    private Date timestamp = new Date();

    private List<LogItem> logItems = new ArrayList<LogItem>();

    private String exception;

    private static Map<Class, OperationType> operationTypeMap = createOperationTypeMap();


    public void setException(String exception) {
        this.exception = exception;
    }

    public TransactionLog toTransactionLog() {
        TransactionLog transactionLog = new TransactionLog();
        transactionLog.setTimestamp(timestamp);

        transactionLog.setOperationType(getOperationType());
        ArrayList<TransactionDetail> transactionDetails = new ArrayList<TransactionDetail>();
        for (LogItem logItem : logItems) {
            transactionDetails.add(createTransactionDetail(transactionLog, logItem));
        }
        transactionLog.setTransactionDetails(transactionDetails);

        transactionLog.setTimespan(calculateTimespan(logItems));

        transactionLog.setException(exception);
        return transactionLog;
    }

    private Long calculateTimespan(List<LogItem> logItems) {
        LogItem lastItem = logItems.get(logItems.size() - 1);
        LogItem firstItem = logItems.get(0);
        return lastItem.getTimestamp().getTime() - firstItem.getTimestamp().getTime();
    }

    private TransactionDetail createTransactionDetail(TransactionLog transactionLog, LogItem logItem) {
        try {
            TransactionDetail transactionDetail = new TransactionDetail();
            transactionDetail.setTransactionLog(transactionLog);
            transactionDetail.setDirection(logItem.getDirection());
            transactionDetail.setDetail(XMLUtils.toXML(logItem.getDetail()));
            transactionDetail.setTimestamp(logItem.getTimestamp());
            return transactionDetail;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

    public OperationType getOperationType() {

        GenericRQ rq = (GenericRQ) logItems.get(0).getDetail();

        if (operationTypeMap.containsKey(rq.getPayload().getClass())) {
            return operationTypeMap.get(rq.getPayload().getClass());
        }

        return OperationType.UNSUPPORTED_OPERATION;
    }

    public void addLogItem(Object detail, Direction direction) {
        logItems.add(new LogItem(direction, detail));
    }

    public boolean isReservation() {
        OperationType operationType = getOperationType();
        return operationType == OperationType.HOTEL_RESERVATION ||
                operationType == OperationType.CANCEL_RESERVATION;
    }

    public String getErspCode() {
        OperationType operationType = getOperationType();
        switch (operationType) {
            case HOTEL_RESERVATION:
                GenericRQ<HotelReservationRQ> resRQ = (GenericRQ<HotelReservationRQ>) logItems.get(0).getDetail();
                return TranslateHelper.find(resRQ.getPayload().getHotelReservationIDs(), UniqueIDType.ERSP);
            case CANCEL_RESERVATION:
                GenericRQ<CancelRQ> cancelRQ = (GenericRQ<CancelRQ>) logItems.get(0).getDetail();
                return TranslateHelper.find(cancelRQ.getPayload().getHotelReservationIDs(), UniqueIDType.ERSP);
        }
        throw new IllegalStateException(String.format("Does not support the operation [%s]", operationType));
    }

    public String getHotelPassport() {
        OperationType operationType = getOperationType();
        switch (operationType) {
            case HOTEL_RESERVATION:
            case CANCEL_RESERVATION:
                GenericRQ rq = (GenericRQ) logItems.get(0).getDetail();
                return rq.getHeader().getProducts().get(0).getId();
        }
        throw new IllegalStateException(String.format("Does not support the operation [%s]", operationType));
    }

    public ReservationLog toReservationLog() {
        ReservationLog reservationLog = new ReservationLog();
        reservationLog.setOperationType(
                OperationType.valueOf(getOperationType().name()));

        for (LogItem logItem : logItems) {
           appendReservationDetail(
                reservationLog, logItem.getDetail(), logItem.getTimestamp());
        }

        return reservationLog;
    }

    private void appendReservationDetail(ReservationLog reservationLog, Object detail, Date timestamp) {
        if (detail != null) {
            ReservationDetail reservationDetail = new ReservationDetail();
            reservationDetail.setTimestamp(timestamp);
            reservationDetail.setDetail(XMLUtils.toXML(detail));
            reservationDetail.setReservationLog(reservationLog);
            reservationLog.appendDetail(reservationDetail);
        }
    }

    private static Map<Class, OperationType> createOperationTypeMap() {

        HashMap<Class, OperationType> map = new HashMap<Class, OperationType>();

        map.put(HotelAvailRQ.class, OperationType.HOTEL_AVAIL);
        map.put(HotelMultiAvailRQ.class, OperationType.HOTEL_AVAIL);
        map.put(HotelReservationRQ.class, OperationType.HOTEL_RESERVATION);
        map.put(CancelRQ.class, OperationType.CANCEL_RESERVATION);

        return map;
    }

    public Date getTimestamp() {
        return logItems.get(0).getTimestamp();
    }

    public boolean hasError() {
        return StringUtils.isNotBlank(exception);
    }
}
